import json

import aiohttp
import asyncio

"""
 示例1 
 aiohttp 是一个基于 asyncio 的异步 HTTP 网络模块
 要实现异步爬取，需要启动协程，而协程则需要借助于 asyncio 里面的事件循环来执行。除了事件循环，asyncio 里面也提供了很多基础的异步操作
 
 异步爬取方法的定义和之前有所不同，在每个异步方法前面统一要加 async 来修饰。
 with as 语句前面同样需要加 async 来修饰。在 Python 中，with as 语句用于声明一个上下文管理器，能够帮我们自动分配和释放资源。而在异步方法中，with as 前面加上 async 代表声明一个支持异步的上下文管理器。
 
 对于一些返回 coroutine 的操作，前面需要加 await 来修饰。比如 response 调用 text 方法，查询 API 可以发现，其返回的是 coroutine 对象，那么前面就要加 await；而对于状态码来说，其返回值就是一个数值类型，那么前面就不需要加 await。所以，这里可以按照实际情况处理，参考官方文档说明，看看其对应的返回值是怎样的类型，然后决定加不加 await 就可以了。
 最后，定义完爬取方法之后，实际上是 main 方法调用了 fetch 方法。要运行的话，必须要启用事件循环，而事件循环就需要使用 asyncio 库，然后使用 run_until_complete 方法来运行。
"""

# async def fetch(session, url):
#     async with session.get(url) as response:
#         return await response.text(), response.status
#
# async def run():
#     async with aiohttp.ClientSession() as session:
#         html, status = await fetch(session, 'https://cuiqingcai.com')
#         print(f'html: {html[:100]}...')
#         print(f'status: {status}')
#
# loop = asyncio.get_event_loop()
# loop.run_until_complete(run())


"""
  示例2 get 请求 url参数设置，借助params参数
  其实际请求的 URL 为 https://httpbin.org/get?name=germey&age=25，其 URL 请求参数就对应了 params 的内容
"""

# async def run():
#     params = {'name': 'germey', 'age': 25}
#     async with aiohttp.ClientSession() as session:
#         async with session.get('https://httpbin.org/get', params=params) as response:
#             print(await response.text())
# asyncio.get_event_loop().run_until_complete(run())


"""
  示例3  POST请求
  对于 POST 表单提交，其对应的请求头的 Content-Type 为 application/x-www-form-urlencoded，我们可以用如下方式来实现
"""

# async def run():
#     data = {'name': 'germey', 'age': 25}
#     async with aiohttp.ClientSession() as session:
#         async with session.post('https://httpbin.org/post', data=data) as response:
#             print(await response.text())
#
# asyncio.get_event_loop().run_until_complete(run())


"""
 示例4 POST请求
 对于 POST JSON 数据提交，其对应的请求头的 Content-Type 为 application/json，我们只需要将 post 方法的 data 参数改成 json 即可，代码示例如下
"""
# async def run():
#     data = {'name': 'germey', 'age': 25}
#     async with aiohttp.ClientSession() as session:
#         async with session.post('https://httpbin.org/post', json=data) as response:
#             print(await response.text())
#
# asyncio.get_event_loop().run_until_complete(run())


"""
  示例5 响应数据
  对于响应来说，我们可以用如下方法分别获取响应的状态码、响应头、响应体、响应体二进制内容、响应体 JSON 结果，示例如下
  这里我们可以看到有些字段前面需要加 await，有的则不需要。其原则是，如果它返回的是一个 coroutine 对象（如 async 修饰的方法），那么前面就要加 await，
  具体可以看 aiohttp 的 API，其链接为：https://docs.aiohttp.org/en/stable/client_reference.html。    
"""


# async def run():
#     data = {'name': 'germey', 'age': 25}
#     async with aiohttp.ClientSession() as session:
#         async with session.post('https://httpbin.org/post', data=data) as response:
#             print('status:', response.status)
#             print('headers:', response.headers)
#             print('body:', await response.text())
#             print('bytes:', await response.read())
#             print('json:', await response.json())
#
# asyncio.get_event_loop().run_until_complete(run())


"""
  示例6
  对于超时设置，我们可以借助 ClientTimeout 对象，比如这里要设置 1 秒的超时，可以这么实现：
  如果超时的话，会抛出 TimeoutError 异常，其类型为 asyncio.TimeoutError，我们再进行异常捕获即可。
"""
# async def run():
#     timeout = aiohttp.ClientTimeout(total=1)
#     async with aiohttp.ClientSession(timeout=timeout) as session:
#         async with session.get('https://httpbin.org/get') as response:
#             print('status:', response.status)
#
# asyncio.get_event_loop().run_until_complete(run())


"""
  示例7 设置并发限制
  由于 aiohttp 可以支持非常大的并发，比如上万、十万、百万都是能做到的，但对于这么大的并发量，目标网站很可能在短时间内无法响应，而且很可能瞬时间将目标网站爬挂掉，所以我们需要控制一下爬取的并发量。
  借助于 asyncio 的 Semaphore 来控制并发量
  
  
  这里我们声明了 CONCURRENCY（代表爬取的最大并发量）为 5，同时声明爬取的目标 URL 为百度。接着，我们借助于 Semaphore 创建了一个信号量对象，将其赋值为 semaphore，这样我们就可以用它来控制最大并发量了。
  怎么使用呢？这里我们把它直接放置在对应的爬取方法里面，使用 async with 语句将 semaphore 作为上下文对象即可。这样的话，信号量可以控制进入爬取的最大协程数量，即我们声明的 CONCURRENCY 的值。

  在 run 方法里面，我们声明了 10000 个 task，将其传递给 gather 方法运行。倘若不加以限制，这 10000 个 task 会被同时执行，并发数量太大。但有了信号量的控制之后，同时运行的 task 的数量最大会被控制在 5 个，这样就能给 aiohttp 限制速度了。
"""


CONCURRENCY = 5
URL = 'https://www.baidu.com'

semaphore = asyncio.Semaphore(CONCURRENCY)

async def scrape_api(session):
    async with semaphore:
        print('scraping', URL)
        try:
            async with session.get(URL) as response:
                await asyncio.sleep(1)  # 模拟耗时操作
                text = await response.text()
                return text
        except Exception as e:
            print(f"Request failed: {e}")
            return None

async def run():
    async with aiohttp.ClientSession() as session:
        tasks = [scrape_api(session) for _ in range(10)]
        # # 用于同时启动多个异步操作，并等待它们全部完成，适合需要并发执行多个异步任务，并一次性获取所有结果的场景。
        results = await asyncio.gather(*tasks)#  results所有请求的响应组成的列表
        print(results)

asyncio.get_event_loop().run_until_complete(run())

if __name__ == '__main__':
    pass